#include <omdcl/machine.hpp>
#include <omdcl/program.hpp>
#include <omd/toolkit.hpp>


union MacInfoUnion{
    char v[1024];
    char str[1024];
    unsigned long l;
    unsigned long vl[3];
    unsigned int u;
    int i;
    cl_device_type type;
};


int query_platform(cl_platform_id &pfid) {
    cl_uint npf;
        // FIXME! take only one flatform...    
    if(CL_SUCCESS!=clGetPlatformIDs(1, &pfid, &npf))
     	throw "failed fetching platform id";
	
	if(npf<=0) throw "no platform found...";
        return npf;
}

MacInfoUnion get_device_info(cl_device_id device, cl_device_info info) {
    MacInfoUnion sinfo;
    if(CL_SUCCESS!=clGetDeviceInfo(device,info,1024,sinfo.v,NULL))
        throw "failed fetching platform info";
    return sinfo;
}

void MDComputeMachine::CollectDeviceInfo() {
    param.push_pair("device_name",
      replace_char(get_device_info(device, CL_DEVICE_NAME).str,' ','_'));
    memsize.global=get_device_info(device,CL_DEVICE_GLOBAL_MEM_SIZE).l;
    memsize.local=get_device_info(device,CL_DEVICE_LOCAL_MEM_SIZE).l;
    memsize.constant=get_device_info(device,CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE).l;
    memsize.maxalloc=get_device_info(device,CL_DEVICE_MAX_MEM_ALLOC_SIZE).l;
    memsize.address=get_device_info(device,CL_DEVICE_ADDRESS_BITS).u;
}

void MDComputeMachine::Create() {
  cl_int err;

  context=clCreateContext(0,1,&device,NULL,NULL,&err);
  assert(err==CL_SUCCESS, "failed creating compute device context on ("+
	 param.string_value("device_name")+")");
  queue=clCreateCommandQueue(context,device,0,&err);
  assert(err==CL_SUCCESS, "failed creating command queue on ("+
							param.string_value("device_name")+")");	
}

MDComputeMachine::MDComputeMachine() {
    set_name("omdcl machine");
    register_class(get_name());
	cl_platform_id pfid;
	cl_uint ndev;
	query_platform(pfid);
	clGetDeviceIDs(pfid,CL_DEVICE_TYPE_ALL,1,&device,&ndev);
	assert(ndev>0, "no device found...");	
    CollectDeviceInfo();
    blog("using device: "+param.string_value("device_name")); 
	Create();
}

MDComputeMachine::MDComputeMachine(string macname) {
    set_name("omdcl machine");
    register_class(get_name());
    cl_platform_id pfid;
	cl_uint ndev;
	query_platform(pfid);
	clGetDeviceIDs(pfid,CL_DEVICE_TYPE_ALL,0,NULL,&ndev);
	assert(ndev>0, "no device found...");
	
	cl_device_id devids[ndev];
	clGetDeviceIDs(pfid,CL_DEVICE_TYPE_ALL,ndev,devids,NULL);
	
	bool devfound=false;
	for(cl_uint i=0;i<ndev;i++) {
	    if(macname==get_device_info(devids[i],CL_DEVICE_NAME).str) {
	        devfound=true;
	        device=devids[i];
	        break;
        }
	}
	assert(devfound, "device ("+macname+") not found...");
	
    CollectDeviceInfo();
    blog("using device: "+param.string_value("device_name"));
	Create();
}

MDComputeMachine::MDComputeMachine(cl_device_type mactype) {
    set_name("omdcl machine");
    register_class(get_name());
    cl_platform_id pfid;
	cl_uint ndev;
	query_platform(pfid);
	clGetDeviceIDs(pfid,CL_DEVICE_TYPE_ALL,0,NULL,&ndev);
	assert(ndev>0, "no device found...");
	
	cl_device_id devids[ndev];
	clGetDeviceIDs(pfid,CL_DEVICE_TYPE_ALL,ndev,devids,NULL);
	
	bool devfound=false;
	for(cl_uint i=0;i<ndev;i++) {
	    if(mactype==get_device_info(devids[i],CL_DEVICE_TYPE).type) {
	        devfound=true;
	        device=devids[i];
	        break;
        }
	}
	assert(devfound, "device type="+as_string((int)mactype)+" not found...");
	
    CollectDeviceInfo();
    blog("using device: "+param.string_value("device_name"));
	Create();
}

MDComputeMachine::~MDComputeMachine() {
    clReleaseCommandQueue(queue);
    clReleaseContext(context);
}

vector<string> MDComputeMachine::GetDeviceList() {
    cl_platform_id pfid;
	cl_uint ndev;
	query_platform(pfid);
	clGetDeviceIDs(pfid,CL_DEVICE_TYPE_ALL,0,NULL,&ndev);
	if(ndev<=0) throw "no device found...";
	
	cl_device_id devids[ndev];
	clGetDeviceIDs(pfid,CL_DEVICE_TYPE_ALL,ndev,devids,NULL);
	
	vector<string> devlist;
	for(cl_uint i=0;i<ndev;i++) {
        devlist.push_back(get_device_info(devids[i],CL_DEVICE_NAME).str);
	}
    
    return devlist;    
}

void MDComputeMachine::PrintInfo(ostream& ost) {
    ost << "dev_name="<<param.string_value("device_name")
        << " global_mem="<<memsize.global/1024/1024<<"M"
        << " local_mem="<<memsize.local/1024<<"K"
        << " const_mem="<<memsize.constant/1024<<"K"
        << std::endl;
}

cl_mem MDComputeMachine::LoadTable(void* buf, size_t len) {
	cl_mem devmem=clCreateBuffer(context,CL_MEM_READ_ONLY,len,NULL,NULL);
	cl_int err=clEnqueueWriteBuffer(queue,devmem,CL_TRUE,0,len,buf,0,NULL,NULL);
	assert(err==CL_SUCCESS, "failed loading table to device memory");
	return devmem;
}

cl_mem MDComputeMachine::CreateBuffer(size_t len, cl_mem_flags flag) {
	cl_int err;
	cl_mem devmem=clCreateBuffer(context,flag,len,NULL,&err);
	assert(err==CL_SUCCESS, "failed allocating memory on device");
	return devmem;
}

void MDComputeMachine::Extract(cl_mem devmem, void* hostmem, size_t len) {
	cl_int err;
	err=clEnqueueReadBuffer(queue,devmem,CL_TRUE,0,len,hostmem,0,NULL,NULL);
	assert(err==CL_SUCCESS, "failed reading output buffer from device");
}

void MDComputeMachine::Release(cl_mem mem) {
	clReleaseMemObject(mem);
}

void MDComputeMachine::Wait() {
	clFinish(queue);
}

void MDComputeMachine::Wait(cl_event& evt) {
	clWaitForEvents(1, &evt);
}

void MDComputeMachine::Wait(int nevt, cl_event* evt) {
	clWaitForEvents(nevt, evt);
}
